
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <errno.h>

#include "data_buffer.h"

static data_buffer_t *all_buffers[BUFFER_POOL_SIZE];

static buffer_pool_t free_pool;
static mssg_queue_t msg_queue;


void init_buffer_pool(void)
{
    if (pthread_mutex_init(&free_pool.mutex, NULL) != 0) {
        perror("mutex_init failed");
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < BUFFER_POOL_SIZE; i++) {
        all_buffers[i] = malloc(sizeof(data_buffer_t));
        if (!all_buffers[i]) {
            perror("malloc failed");
            exit(EXIT_FAILURE);
        }
        free_pool.buffers[i] = all_buffers[i];
    }

    free_pool.count = BUFFER_POOL_SIZE;
    free_pool.front = 0;
    free_pool.rear = BUFFER_POOL_SIZE - 1;
}

data_buffer_t *get_buffer(void)
{
    pthread_mutex_lock(&free_pool.mutex);
    data_buffer_t *buf = NULL;
    if (free_pool.count > 0) {
        buf = free_pool.buffers[free_pool.front];
        free_pool.front = (free_pool.front + 1) % BUFFER_POOL_SIZE;
        free_pool.count--;
    }
    pthread_mutex_unlock(&free_pool.mutex);
    return buf;
}

void return_buffer(data_buffer_t *buf)
{
    pthread_mutex_lock(&free_pool.mutex);
    if (free_pool.count < BUFFER_POOL_SIZE) {
        free_pool.rear = (free_pool.rear + 1) % BUFFER_POOL_SIZE;
        free_pool.buffers[free_pool.rear] = buf;
        free_pool.count++;
    }
    pthread_mutex_unlock(&free_pool.mutex);
}

void init_message_queue(void)
{
    if (pthread_mutex_init(&msg_queue.mutex, NULL) != 0 || pthread_cond_init(&msg_queue.cond, NULL) != 0) {
        perror("mutex/cond_init failed");
        exit(EXIT_FAILURE);
    }

    msg_queue.count = 0;
    msg_queue.front = 0;
    msg_queue.rear = BUFFER_POOL_SIZE - 1;
}

void enqueue_message(data_buffer_t *buf)
{
    pthread_mutex_lock(&msg_queue.mutex);

    if (msg_queue.count < BUFFER_POOL_SIZE) {
        msg_queue.rear = (msg_queue.rear + 1) % BUFFER_POOL_SIZE;
        msg_queue.queue[msg_queue.rear] = buf;
        msg_queue.count++;

        if (msg_queue.count == 1) {
            pthread_cond_signal(&msg_queue.cond);
        }
    } else {
        return_buffer(buf);
    }

    pthread_mutex_unlock(&msg_queue.mutex);
}

void notify_queue_message(void)
{
    pthread_mutex_lock(&msg_queue.mutex);
    pthread_cond_signal(&msg_queue.cond);
    pthread_mutex_unlock(&msg_queue.mutex);
}

void deinit_buffer_pool(void)
{
    for (int i = 0; i < BUFFER_POOL_SIZE; i++) {
        if (all_buffers[i]) {
            free(all_buffers[i]);
            all_buffers[i] = NULL;
        }
    }
    for (int i = 0; i < BUFFER_POOL_SIZE; i++) {
        free_pool.buffers[i] = NULL;
    }
    free_pool.count = 0;
    pthread_mutex_destroy(&free_pool.mutex);
}

void deinit_message_queue(void)
{
    for (int i = 0; i < msg_queue.count; i++) {
        msg_queue.queue[i] = NULL;
    }
    msg_queue.count = 0;
    pthread_mutex_destroy(&msg_queue.mutex);
    pthread_cond_destroy(&msg_queue.cond);
}

data_buffer_t *dequeue_message_timed(int timeout_ms)
{
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_nsec += timeout_ms * 1000000;
    if (ts.tv_nsec >= 1000000000) {
        ts.tv_sec += 1;
        ts.tv_nsec -= 1000000000;
    }

    pthread_mutex_lock(&msg_queue.mutex);

    if (msg_queue.count == 0) {
        if (pthread_cond_timedwait(&msg_queue.cond, &msg_queue.mutex, &ts) == ETIMEDOUT) {
        } else {
            /* no cond, do nothing */
        }
    } else {
        /* count > 1, get buffer directly */
    }

    data_buffer_t *buf = NULL;
    if (msg_queue.count > 0) {
        buf = msg_queue.queue[msg_queue.front];
        msg_queue.front = (msg_queue.front + 1) % BUFFER_POOL_SIZE;
        msg_queue.count--;
    } else {
        /* no availble buffer, return nullptr */
    }

    pthread_mutex_unlock(&msg_queue.mutex);
    return buf;
}
